// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Core.Mapping.ViewGeneration.Structures
{
    using System.Collections.Generic;
    using System.Data.Entity.Core.Common.CommandTrees;
    using System.Data.Entity.Core.Common.Utils;
    using System.Data.Entity.Core.Mapping.ViewGeneration.CqlGeneration;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Text;

    // <summary>
    // This class represents the constants or members that that can be referenced in a C or S Cell query.
    // In addition to fields, may represent constants such as types of fields, booleans, etc.
    // </summary>
    internal abstract class ProjectedSlot : InternalBase, IEquatable<ProjectedSlot>
    {
        internal static readonly IEqualityComparer<ProjectedSlot> EqualityComparer = new Comparer();

        // <summary>
        // Returns true if this is semantically equivalent to <paramref name="right" />.
        // </summary>
        protected virtual bool IsEqualTo(ProjectedSlot right)
        {
            return base.Equals(right);
        }

        protected virtual int GetHash()
        {
            return base.GetHashCode();
        }

        public bool Equals(ProjectedSlot right)
        {
            return EqualityComparer.Equals(this, right);
        }

        public override bool Equals(object obj)
        {
            var right = obj as ProjectedSlot;
            if (obj == null)
            {
                return false;
            }
            return Equals(right);
        }

        public override int GetHashCode()
        {
            return EqualityComparer.GetHashCode(this);
        }

        // <summary>
        // Creates new <see cref="ProjectedSlot" /> that is qualified with <paramref name="block" />.CqlAlias.
        // If current slot is composite (such as <see cref="CaseStatementProjectedSlot" />, then this method recursively qualifies all parts
        // and returns a new deeply qualified slot (as opposed to <see cref="CqlBlock.QualifySlotWithBlockAlias" />).
        // </summary>
        internal virtual ProjectedSlot DeepQualify(CqlBlock block)
        {
            var result = new QualifiedSlot(block, this);
            return result;
        }

        // <summary>
        // Returns the alias corresponding to the slot based on the <paramref name="outputMember" />, e.g., "CPerson1_pid".
        // Derived classes may override this behavior and produce aliases that don't depend on <paramref name="outputMember" />.
        // </summary>
        internal virtual string GetCqlFieldAlias(MemberPath outputMember)
        {
            return outputMember.CqlFieldAlias;
        }

        // <summary>
        // Given the slot and the <paramref name="blockAlias" />, generates eSQL corresponding to the slot.
        // If slot is a qualified slot, <paramref name="blockAlias" /> is ignored. Returns the modified
        // <paramref
        //     name="builder" />
        // .
        // </summary>
        // <param name="outputMember"> outputMember is non-null if this slot is not a constant slot </param>
        // <param name="indentLevel"> indicates the appropriate indentation level (method can ignore it) </param>
        internal abstract StringBuilder AsEsql(StringBuilder builder, MemberPath outputMember, string blockAlias, int indentLevel);

        // <summary>
        // Given the slot and the input <paramref name="row" />, generates CQT corresponding to the slot.
        // </summary>
        internal abstract DbExpression AsCqt(DbExpression row, MemberPath outputMember);

        // <summary>
        // Given fields in <paramref name="slots1" /> and <paramref name="slots2" />, remap and merge them.
        // </summary>
        internal static bool TryMergeRemapSlots(ProjectedSlot[] slots1, ProjectedSlot[] slots2, out ProjectedSlot[] result)
        {
            // First merge them and then remap them
            ProjectedSlot[] mergedSlots;
            if (!TryMergeSlots(slots1, slots2, out mergedSlots))
            {
                result = null;
                return false;
            }

            result = mergedSlots;
            return true;
        }

        // <summary>
        // Given two lists <paramref name="slots1" /> and <paramref name="slots2" />, merge them and returnthe resulting slots,
        // i.e., empty slots from one are overridden by the slots from the other.
        // </summary>
        private static bool TryMergeSlots(ProjectedSlot[] slots1, ProjectedSlot[] slots2, out ProjectedSlot[] slots)
        {
            Debug.Assert(slots1.Length == slots2.Length, "Merged slots of two cells must be same size");
            slots = new ProjectedSlot[slots1.Length];

            for (var i = 0; i < slots.Length; i++)
            {
                var slot1 = slots1[i];
                var slot2 = slots2[i];
                if (slot1 == null)
                {
                    slots[i] = slot2;
                }
                else if (slot2 == null)
                {
                    slots[i] = slot1;
                }
                else
                {
                    // Both slots are non-null: Either both are the same
                    // members or one of them is a constant
                    // Note: if both are constants (even different constants)
                    // it does not matter which one we pick because the CASE statement will override it
                    var memberSlot1 = slot1 as MemberProjectedSlot;
                    var memberSlot2 = slot2 as MemberProjectedSlot;

                    if (memberSlot1 != null
                        && memberSlot2 != null
                        &&
                        false == EqualityComparer.Equals(memberSlot1, memberSlot2))
                    {
                        // Illegal combination of slots; non-constant fields disagree
                        return false;
                    }

                    // If one of them is a field we have to get the field
                    var pickedSlot = (memberSlot1 != null) ? slot1 : slot2;
                    slots[i] = pickedSlot;
                }
            }
            return true;
        }

        // <summary>
        // A class that can compare slots based on their contents.
        // </summary>
        private sealed class Comparer : IEqualityComparer<ProjectedSlot>
        {
            // <summary>
            // Returns true if <paramref name="left" /> and <paramref name="right" /> are semantically equivalent.
            // </summary>
            public bool Equals(ProjectedSlot left, ProjectedSlot right)
            {
                // Quick check with references
                if (ReferenceEquals(left, right))
                {
                    // Gets the Null and Undefined case as well
                    return true;
                }
                // One of them is non-null at least. So if the other one is
                // null, we cannot be equal
                if (left == null
                    || right == null)
                {
                    return false;
                }
                // Both are non-null at this point
                return left.IsEqualTo(right);
            }

            public int GetHashCode(ProjectedSlot key)
            {
                return key.GetHash();
            }
        }
    }
}
